<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8">
		<title>11、Set和Map数据结构——set</title>
	</head>
	<body>
		<div></div>
		<script type="text/javascript">
			//一、set
			//1.基本用法
			//ES6提供了新的数据结构Set.它类似于数组，但是成员的值都是唯一的，没有重复的值。
			//Set本身是一个构造函数，用来生成Set数据结构。
			const s = new Set();
			[2,3,5,4,5,2,2].forEach(x=>s.add(x));
			for(let i of s){
				console.log(i)  //2  3  5  4
			}
			//上面代码通过add方法向Set结构加入成员，结果表明Set结构不会添加重复的值。
			//Set函数可以接受一个数组（或者具有 iterable 接口的其他数据结构）作为参数，用来初始化。
			//例一
			const set = new Set([1,2,3,4,4])
			console.log([...set])  //[1, 2, 3, 4]
			//例二
			const items = new Set([1,2,3,4,5,5,5,5])
			console.log('items.size:'+items.size)  //items.size:5
			//例三
			function divs(){
				return [...document.querySelectorAll('div')];
			}
			const set_1 = new Set(divs());
			console.log(set_1.size)  //1
			//类似于
			divs().forEach(div => set_1.add(div))
			console.log(set_1.size)  //1
			//上面代码中，例一和例二都是Set函数接受数组作为参数，例三室接受类似数组的对象作为参数。
			//上面代码中，也展示了一种去除数组重复成员的方法。
			//去除数组的重复成员
//			[...new Set(array)]
			//向 Set 加入值的时候，不会发生类型转换，所以5和"5"是两个不同的值。Set 内部判断两个值是否不同，使用的算法叫做“Same-value equality”，它类似于精确相等运算符（===），主要的区别是NaN等于自身，而精确相等运算符认为NaN不等于自身。
			let set_2 = new Set();
			let a = NaN;
			let b = NaN;
			set_2.add(a);
			set_2.add(b);
			console.log(set_2)  //{NaN}
			//上面代码向 Set 实例添加了两个NaN，但是只能加入一个。这表明，在 Set 内部，两个NaN是相等。
			//另外，两个对象总是不相等的。
			let set_3 = new Set();
			set_3.add({});
			console.log(set_3.size);// 1
			
			set_3.add({});
			console.log(set_3)  //{Object {}, Object {}}
			console.log(set_3.size)  //2
			//上面代码表示，由于两个空对象不相等，所以它们被视为两个值。
			
			
			//2.Set实例的属性和方法
			//Set 结构的实例有以下属性。
//		    Set.prototype.constructor：构造函数，默认就是Set函数。
//		    Set.prototype.size：返回Set实例的成员总数。
			//Set 实例的方法分为两大类：操作方法（用于操作数据）和遍历方法（用于遍历成员）。下面先介绍四个操作方法。
//		    add(value)：添加某个值，返回Set结构本身。
//		    delete(value)：删除某个值，返回一个布尔值，表示删除是否成功。
//		    has(value)：返回一个布尔值，表示该值是否为Set的成员。
//		    clear()：清除所有成员，没有返回值。
			//上面这些属性和方法的实例如下
			const s_1 = new Set()
			s_1.add(1).add(2).add(2);  //注意2被加入两次
			console.log(s_1)
 
			console.log(s_1.has(1))  //true
			console.log(s_1.has(2))  //true
			console.log(s_1.has(3))  //false
			
			s_1.delete(2)
			console.log(s_1.has(2))  //false
			//下面是一个对比，看看在判断是否包括一个键上面，Object结构和Set结构的写法不同。
			//对象的写法
//			const properties = {
//				'width':1,
//				'height':1
//			};
//			if(properties[someName]){
//				
//			}
//			//Set的写法
//			const properties_1 = new Set();
//			properties_1.add('width');
//			properties_1.add('height');
//			if(properties_1.has(someName)){
//				
//			}
//			
			//Array.from方法可以将Set结构转为数组。
			const items_1 = new Set([1,2,3,4,5])
			const array_1 = Array.from(items_1);
			console.log(array_1)  //[1, 2, 3, 4, 5]
			//这就提供了去除数组重复成员的另一种方法。
			function dedupe(array){
				return Array.from(new Set(array));
			}
			console.log(dedupe([1,1,2,3])) // [1, 2, 3]
			
			
			//3.遍历操作
			//Set 结构的实例有四个遍历方法，可以用于遍历成员。
			
//		    keys()：返回键名的遍历器
//		    values()：返回键值的遍历器
//		    entries()：返回键值对的遍历器
//		    forEach()：使用回调函数遍历每个成员
			//需要特别指出的是，Set的遍历顺序就是插入顺序。这个特性有时非常有用，比如使用Set保存一个回调函数列表，调用时就能保证按照添加顺序调用。
			
			//(1).keys()、values()、entries()
			//keys方法、values方法、entries方法返回的都是遍历器对象（详见《Iterator 对象》一章）。由于 Set 结构没有键名，只有键值（或者说键名和键值是同一个值），所以keys方法和values方法的行为完全一致。
			let set_4 = new Set(['red','green','blue']);
			for(let item of set_4.keys()){
				console.log(item) //red   green   blue
			}
			
			for(let item of set_4.values()){
				console.log(item); //red   green   blue
			}
			
			for(let item of set_4.entries()){
				console.log(item) //["red", "red"]    ["green", "green"]   ["blue", "blue"]
			}
			//上面代码中，entries方法返回的遍历器，同时包括键名和键值，所以每次输出一个数组，它的两个成员完全相等。
			//Set 结构的实例默认可遍历，它的默认遍历器生成函数就是它的values方法。
			Set.prototype[Symbol.iterator] === Set.prototype.values
			//这意味着，可以省略values方法，直接用for...of循环遍历 Set。
			let set_5 = new Set(['red','green','blue']);
			for(let x of set_5){
				console.log(x)  //red    green    blue
			}
			
			//(2).forEach()
			//Set 结构的实例与数组一样，也拥有forEach方法，用于对每个成员执行某种操作，没有返回值。
			let set_6 = new Set([1,4,9])
			set_6.forEach((value,key)=>console.log(key+':'+value))
			//1:1
			//4:4
			//9:9
			//上面代码说明，forEach方法的参数就是一个处理函数。该函数的参数与数组的forEach一致，依次为键值、键名、集合本身（上例省略了该参数）。这里需要注意，Set 结构的键名就是键值（两者是同一个值），因此第一个参数与第二个参数的值永远都是一样的。
			//另外，forEach方法还可以有第二个参数，表示绑定处理函数内部的this对象。
			
			
			//(3).遍历的应用
			//扩展运算符（...）内部使用for...of循环，所以也可以用于 Set 结构。
			let set_7 = new Set(['red','green','blue']);
			let arr = [...set_7];
			console.log(arr)  //["red", "green", "blue"]
			//扩展运算符和 Set 结构相结合，就可以去除数组的重复成员。
			let arr_1 = [3,5,2,2,5,5];
			let unique = [...new Set(arr_1)]
			console.log(unique) //[3,5,2]
			//而且，数组的map和filter方法也可以用于 Set 了。
			let set_8 = new Set([1,2,3])
			set_8 = new Set([...set_8].map(x => x*2))
			console.log(set_8)  //返回Set结构：{2, 4, 6}
			
			let set_9 = new Set([1,2,3,4,5]);
			set_9 = new Set([...set].filter(x=>(x%2) == 0 ))
			console.log(set_9)  //2,4
			
			//因此使用 Set 可以很容易地实现并集（Union）、交集（Intersect）和差集（Difference）。
			let a_1 = new Set([1,2,3]);
			let b_1 = new Set([4,3,2]);
			//并集
			let union = new Set([...a_1,...b_1]);
			console.log(union)
			//Set(4) {1, 2, 3, 4}
			
			//交集
			let intersect = new Set([...a_1].filter(x => b_1.has(x)));
			console.log(intersect)  //Set(2) {2, 3}
			
			//差集
			let difference = new Set([...a_1].filter(x => !b_1.has(x)));
			console.log(difference)  //Set(1) {1}
			//如果想在遍历操作中，同步改变原来的 Set 结构，目前没有直接的方法，但有两种变通方法。一种是利用原 Set 结构映射出一个新的结构，然后赋值给原来的 Set 结构；另一种是利用Array.from方法。
			//方法一
			let set_10 = new Set([1,2,3]);
			set_10 = new Set([...set_10].map(val=>val*2));
			console.log(set_10)  //Set(3) {2, 4, 6}
			
			//方法二
			let set_11 = new Set([1,2,3]);
			set_11 = new Set(Array.from(set_11,val=>val*2));
			console.log(set_11) //Set(3) {2, 4, 6}
			//上面代码提供了两种方法，直接在遍历操作中改变原来的 Set 结构。
			
		</script> 
	</body>
</html>
